/**
 * ======================================================================
 * 【基本说明】：
 * 本文件是MobileIMSDK-Web版服务端SDK的核心入口，以回调的方式向外暴露各种事件通知、实用方法等。
 *
 * 【使用方法】：详见Demo主文件www.js的代码实现即可
 *   // MobileIMSDK-Web版的SDK主入口
 *   var imServerSDK = require('../j_imserver_sdk/im-server-sdk');
 *   // 创建MobileIMSDK-Web版服务端的服务
 *   imServerSDK.createServer(server);
 *   // 设置连接认证回调
 *   imServerSDK.setCallBack_checkAuthToken(fn);
 *   // 设置个人认证后信息的处理回调
 *   imServerSDK.setCallBack_authedInfo(fn);
 *   // 设置用户上线回调
 *   imServerSDK.setCallBack_userOnline(fn);
 *   // 设置用户离线回调
 *   imServerSDK.setCallBack_userOffline(fn);
 *   // 设置C2S消息回调
 *   imServerSDK.setCallBack_receivedC2SMessage(fn);
 *   // 设置C2C消息回调
 *   imServerSDK.setCallBack_transferedC2CMessage(fn);
 *   // 设置离线消息处理回调
 *   imServerSDK.setCallBack_processC2COfflineMessage(fn);
 *
 * 【补充说明】：
 * 原本是希望像其它OOP语言一样，写一个IMServerSDK的类，通过module.export=IMServerSDK
 * 暴露给外部来使用，但因为Socket.io的“require('socket.io')(server)”这种方法，需要传参
 * 'server'，而在外部调用时通过“var imServerSDK = require('../j_imserver_sdk/im-server-sdk')(server);”
 * 的结果是imServerSDK确是undefine！！这太诡异了，而外部换成普通的new IMServerSDK(server)时，又会导致IMServerSDK
 * 内部的socketio对象找不到on、use这些函数（报not a function）！目前干脆换成目前这种传统的JS代码方式写，
 * 这样更顺手，所以不应硬生生地把OOP理论带入到Nodejs，因为JS语言天生就是为了简单，或许回归本真才是最佳实践！
 *
 * Created by Jack Jiang on 16-9-10.
 * ======================================================================
 */

var SendUtilsLocal = require('./send-util-local');
var SendUtilsGlobal = require('./send-util-global');
var config = require('../j_conf/config');
var logger = require('../j_conf/log').logger;
var ErrorFactory = require('./error-factory');
var UserProcessorClass = require('./user-processor');
var ProtocalType = require('./protocal-type');
var ProtocalFactory = require('./protocal-factory');

// 内部变量：MQ中间件服务提供者（用于异构消息的处理）
var imMQProvider = config.im_bridge_enabled? require('./im-mq-provider'): null;
// 内部变量：在线列表
var userProcessor = new UserProcessorClass();
// 内部变量：socket.io的服务端连接实xx例
var socketio = null;

// 以下回调方法由sdk的应用层开发者设置哦
var callBack_checkAuthToken = null;
var callBack_userOnline = null;
var callBack_userOffline = null;
var callBack_receivedC2SMessage = null;       // 此回调相当于APP的IM服务端回调方法：onTransBuffer_CallBack
var callBack_transferedC2CMessage = null;     // 此回调相当于APP的IM服务端回调方法：onTransBuffer_C2C_CallBack
var callBack_processC2COfflineMessage = null;
var callBack_authedInfo = null;



// 内部方法：初始化整个服务端IM的SDK
function _init(){

    /** -----------------------------------------------------------------------------------
     * 启动MQ中间件服务（开启桥接跨服消息的处理）.
     */

    // 需要与APP版的互通
    if (config.im_bridge_enabled) {

            // 设置MQ桥接消息回调：以下回调函数将在nodejs端的MQProvider中当收到MQ的消息时进行回调，应用层通过此回调来进行消息转发处理
            // 本回调的返回值说明：true表示此消息正确处理完成，false表示未成功处理（MQ会将原消息重放回队列以便下次再取到）。
        imMQProvider.setCallBack_forWork(function (p) {
            try {
                // 如果nodejs端通过桥接（收到的是APP端发过来的消息）收到的消息不为空则尝试在本nodejs机器发送（给用户）
                if (p) {
                    logger.debug('[IMSDK-MQ↓]【桥接收】callBack_forWork回调中收到Protocal对象：[%s], 马上通过本机的WebIM发送之！'
                        , JSON.stringify(p));

                    // 发送消息（此方法内的sendData实际上会通过回调处理在线和不在线的情况，只管调用就是了）
                    // ## Bug fixed by js 20160922: 之前错误地把p.to写成了p.toLocalString而导致收消息时
                    // ## - 出现"RabbitMQ Error Code 504"(原因是错误地代码使得sendProtocalForOnlineOrOffline发生异常，
                    // ## - 因没有try catch而蔓延至MQProvider里的处理代码)!
                    //sendDataLocal4OnlineOrOffline(userProcessor.getSession(p.to), p);
                    sendDataLocal4OnlineOrOffline(null, p, null);// 因为桥接消息是由服务端代码，所以第一个参数（发送者的socket引用）自然填null
                    return true;
                }
                else {
                    logger.error('[IMSDK-MQ↓]【桥接收】callBack_forWork回调中收到无效的Protocal对象：%s，【本条处理没有继续】', JSON.stringify(p));
                    //return false;
                    return true;// 【注意】尽量不要返回false，不然消息被放回队列后容易死循环，出错作好记录备查即可！
                }
            }
            catch (e) {
                logger.error('[IMSDK-MQ↓]【桥接收】callBack_forWork回调中出错了，消息内容：[%s]，错误原因：[%s]【本条处理没有继续】'
                    , JSON.stringify(p), JSON.stringify(e));
                return true;// 【注意】尽量不要返回false，不然消息被放回队列后容易死循环，出错作好记录备查即可！
            }

        });
        // 启动MQ中件件服务
        imMQProvider.start();

        logger.info("[IMSDK] 配置项：已开启与MobileIMSDK APP的互通 √ ！");
    }
    else{
        logger.info("[IMSDK] 配置项：未开启与MobileIMSDK APP的互通 × ！");
    }


    /** -----------------------------------------------------------------------------------
     * 处理WebSocket认证相关的事项.
     */

    socketio.use(function (socket, next) {
        //var token = socket.request.query.token;
        var token = socket.handshake.query.token;
        logger.debug('[IMSDK] 客户端正在发起连接并提交了认证token=%s', token);
        var loginInfo = JSON.parse(token);

        // SDK的持有者是否设置了认证方法
        if(callBack_checkAuthToken) {
            // 对客户端的连接进行合法身份认证和检查
            callBack_checkAuthToken(loginInfo, function (err, authorized) {
                // 认证实败
                if (err || !authorized) {
                    // 将未认证情况通过error事件传回给客户端
                    next(new Error(JSON.stringify(ErrorFactory.CodeUnauthed)));

                    // If the socket didn't authenticate, disconnect it
                    // 补充：因Socket.io的底层网络机制，客户端此时不能主动关闭连接，不然服务
                    // 端无法触发它的disconnect事件（客户端事件也会不正常）。而服务端也不要
                    // 立即关闭，延迟几秒以备客户端处理完上面的Error认证事件
                    SendUtilsLocal.lazyCloseSocket(socket, '[IMSDK] 客户端：' + socket.id + ' 因连接未获认证，马上关闭之... ')
                }
                // 认证成功
                else {
                    // 将用户id作为一个socket的合法认证身份先给存起来（换句话：没有userId的socket肯定是非法的）
                    socket.userId = loginInfo.loginUserId;
                    // 将用户认证完的个人信息存储起来备用 - add by js 20161027
                    socket.authedinfo = callBack_authedInfo(loginInfo);

                    logger.debug('[IMSDK] 新用户 %s (skt.id=%s)已经上线成功！', socket.userId, socket.id);

                    // 将认证成功的情况通过error事件发射回客户端
                    next(new Error(JSON.stringify(ErrorFactory.CodeAuthed)));
                    // 将用户信息放入到在线列表中（理论上：每一个存放在在线列表中的session都对应了user_id）
                    userProcessor.putUser(socket.userId, socket);

                    // @@ 用户上线回调或上通通知！！
                    if(callBack_userOnline) {
                        callBack_userOnline(socket.userId, socket);
                    }
                }

                // 没有此行代码，上面即使执行了socket.disconnet也同样不会停止客户端的自动重连，
                // 而且不会真正触发disconnect事件，估计跟底层的事件链有关系，具体原因理空再深究
                next();
            });
        }
        else{
            logger.error('[IMSDK] 【错误】没有设置用户认证函数：callBack_checkAuthToken！！！');
        }
    });


    /** -----------------------------------------------------------------------------------
     * 处理WebSocket和聊天相关的事件.
     */

    socketio.on('connection', function (socket) {
        logger.debug('[IMSDK] [E]客户端：%s 已经建立连接！', socket.id);

        // 客户端一对一聊天的通用数据发送事件
        socket.on(ProtocalType.C_IMEVT_COMMON$DATA, function (p) {
            logger.debug('[IMSDK] >> [E]服务端收到本客户端：%s(name=%s) C_IMEVT_COMMON$DATA 事件，即将送给指定的人(%s) 。。。'
                , socket.id, socket.userId, JSON.stringify(p));

            // 只有认证了的socket才允许
            if (socket.userId) {

                // 【C2S数据】客户端发给服务端的消息
                if('0'== p.to) {
                    logger.debug('[IMSDK] >> [E]【C2S】服务端收到本客户端：%s(name=%s) C_IMEVT_COMMON$DATA 事件，此条是C2S消息！'
                        , socket.id, socket.userId);

                    // @@ 业务层或者利用回调来处理C2S消息！！
                    if(callBack_receivedC2SMessage){
                        callBack_receivedC2SMessage(p, socket);
                    }
                }
                // 【C2C数据】客户端发给客户端的消息
                else{
                    sendDataGlobal(socket, p, callBack_processC2COfflineMessage);
                }
            }
            else{
                logger.error('[IMSDK] >> [E]本客户端：%s(name=%s) C_IMEVT_COMMON$DATA 事件，奇怪的是本scket没有认证！！！非法！！'
                    , socket.id, socket.userId, JSON.stringify(p));

                // 将非法连接被拒绝服务的消息反馈给此连接客户端（以备它在ui上给用户以友好的反馈）,
                // to_user_id此时就没有意义了，因为此处服务端要返回消息原因就是它是不存在user_id的非法连接
                var ok = SendUtilsLocal.sendData1(socket, ProtocalFactory.createIllegalResponse('illegal_no_userid!'), null);
                // 同时关掉它的网络连接
                // 补充：因Socket.io的底层网络机制，客户端此时不能主动关闭连接，不然服务
                // 端无法触发它的disconnect事件（客户端事件也会不正常）。而服务端也不要
                // 立即关闭，延迟几秒以备客户端处理完上面的消息事件
                SendUtilsLocal.lazyCloseSocket(socket, '[IMSDK] [E]【注意】socket.id='
                    +socket.id+'是非法连接，已被拒绝服务并被关闭了socket连接！(ok='+ok+')');
            }
        });

        // when the user disconnects.. perform this
        socket.on('disconnect', function () {
            // userId不为空即表示是成功登陆的用户，否则是没有认证成功的用户，就不需要通知其它人了
            if (socket.userId) {
                // 将此用户从在线列表中移除
                var ok = userProcessor.removeUser(socket.userId);
                logger.debug('[IMSDK] [E]用户 %s (skt.id=%s) 连接已断开，是否成功从在线列表中移除？%s'
                    , socket.userId, socket.id, ok);

                // @@ 用户离线回调或离通通知！！
                if(callBack_userOffline){
                    callBack_userOffline(socket.userId, socket);
                }
            }
            else {
                logger.debug('[IMSDK] [E]skt.id=%s 连接已断开（该连接中没有认证信息，可能来自未认证成功要关闭的连接！）'
                    , socket.id);
            }

            logger.debug('[IMSDK] [E]客户端：%s 已断开连接(socket.name=%s)，【END】', socket.id, socket.userId);
        });
    });
}

/**
 * 一个发送（仅限于本机用户）消息的方便方法（用户不在线时的处理，会参sendData方法内通过回调，本方法只管调用就行了）
 * 提炼出本方法的目的，是希望本机用户消息的发送和work桥接收到的消息的代发代码统一，便于维护.
 * <p>
 * 本方法的消接收者仅限且必须限于本机范围内，是否需要作为MQ桥接消息不在本方法内考虑（应由本方法的外层方法去考虑），
 * 否则将导致桥接消息的发送混乱。
 *
 * @param senderSocket 消息发送者的socket（当为桥接消息由服务端代发时，本socket参数为null），本参数主要为了log输出
 * @param p 要发送给对方的消息包（接收者的socket将会通过p.to 在 userProcessor中的用户列表找到）
 * @param arg_callBack_processC2COfflineMessage 非必须参数，用于指定消息没有在线发送成功时的离线消息回调处理，如果本参数为空则
 * 函数内将使用默认的callBack_processC2COfflineMessage作为离线消息的回调处理函数。【提示】：有些消息如果您不希望被离线存储（比如下线通知），
 * 则可以通过自定义本离线回调（比如让你的自写义回调只打出Log而不使用默认回调里的持久化到数据库等）来实现就行了！
 * @return true表示发出完成
 */
// 【友情提示】：
// 如果是被转发的桥接消息的情况，而您不希望这个消息被离线存储（只希望接收者在线的话就发，不在线就不发）时，
// 您可以考虑将arg_callBack_processC2COfflineMessage参数传入您自定义的函数（比如让你的自写义回调只打出
// Log而不使用默认回调里的持久化到数据库等），那么这样也就实现了不需要将某类不必要的消息持久化了，比如APP那
// 边发过来的下线通知，这种通知只是实时用意义，如果接收者不在线还要离线存它有何意义呢？！
function sendDataLocal4OnlineOrOffline(senderSocket, p, arg_callBack_processC2COfflineMessage){

    // ###### Bug FIX: 20170918 START
    // 如果回调函数arg_callBack_processC2COfflineMessage为空，则使用系统默认的
    // callBack_processC2COfflineMessage作为回调函数，否则使用用户自定义的回调函数
    if(arg_callBack_processC2COfflineMessage){
        // do nothing
    }
    else {
        arg_callBack_processC2COfflineMessage = callBack_processC2COfflineMessage;
    }
    // ###### Bug FIX: 20170918 END

    var ok = SendUtilsLocal.sendData2(userProcessor, p, arg_callBack_processC2COfflineMessage);
    // 以下代码主要为了Log输出
    if (senderSocket) {
        logger.debug('[IMSDK] [E]本客户端：%s(name=%s) 的 C_IMEVT_COMMON$DATA 事件，实时发送给指定的人(%s) 了吗？【%d】'
            , senderSocket.id, senderSocket.userId, p.to, ok);
    }
    else {
        logger.debug('[IMSDK] [E]【C2S消息】 C_IMEVT_COMMON$DATA 事件，实时发送给指定的人(%s) 了吗？【%d】'
            ,  p.to, ok);
    }

    if(ok){
        // @@ C2C消息发送成功后的回调实现（此回调的作为像APP的IM服务端端一样通常用作用户行为和聊
        //    天内容抓取之用）本回调的作用参考APP的IM服务端的onTransBuffer_C2C_CallBack(..)
        if(callBack_transferedC2CMessage){
            callBack_transferedC2CMessage(p);
        }
    }
    // * 离线回调将在 SendUtilsLocal.sendData2 里被调用，以下代码就不需要了！
    //else{
    //    // @@ C2C消息发送失败后的回调实现！（应用层可能需要做离线消息处理！）
    //    if(callBack_processC2COfflineMessage){
    //        callBack_processC2COfflineMessage(p);
    //    }
    //}

    return ok;
}

/**
 * 一个发送（本机+跨机器通信）消息的方便方法，此方法封装了服务端中转发送(C2C)、服务器主动发送(S2C)
 * 消息的所有逻辑（包括当接收者不在本机在线列表时的桥接处理等）。
 * <p>
 * <b>提示：</b>本方法可用于应用层调用，从而实现服务端向客户端主动发送消息的能力（且支持跨机器的消息能力）。
 *
 * @param socket 消息发起者的socket句柄（而非接收者哦），此句柄在方法中主要用于log信息的提取。
 * 当服务端主动发起消息（即S2C模式的消息，而非C2C）时本socket参数请填null就行了
 * @param p
 * @param arg_callBack_processC2COfflineMessage 非必须参数，用于指定消息没有在线发送成功时的离线消息回调处理，如果本参数为空则
 * 函数内将使用默认的callBack_processC2COfflineMessage作为离线消息的回调处理函数。【提示】：有些消息如果您不希望被离线存储（比如下线通知），
 * 则可以通过自定义本离线回调（比如让你的自写义回调只打出Log而不使用默认回调里的持久化到数据库等）来实现就行了！
 */
// 【友情提示】：
// 如果是被转发的桥接消息的情况，而您不希望这个消息被离线存储（只希望接收者在线的话就发，不在线就不发）时，
// 您可以考虑将arg_callBack_processC2COfflineMessage参数传入您自定义的函数（比如让你的自写义回调只打出
// Log而不使用默认回调里的持久化到数据库等），那么这样也就实现了不需要将某类不必要的消息持久化了，比如APP那
// 边发过来的下线通知，这种通知只是实时用意义，如果接收者不在线还要离线存它有何意义呢？！
function sendDataGlobal(socket, p, arg_callBack_processC2COfflineMessage){

    // ###### Bug FIX: 20170918 START
    // 如果回调函数arg_callBack_processC2COfflineMessage为空，则使用系统默认的
    // callBack_processC2COfflineMessage作为回调函数，否则使用用户自定义的回调函数
    if(arg_callBack_processC2COfflineMessage){
        // do nothing
    }
    else {
        arg_callBack_processC2COfflineMessage = callBack_processC2COfflineMessage;
    }
    // ###### Bug FIX: 20170918 END

    // ** 【已启用与APP端的互通 且 本机不在线的情况下 就尝试转为桥接发送】
    // FIXME 【以后或许要实现】【第二阶段集群实现】时要修改以下在线状态判断为全局所有用户在线列表中的结果（而不只是本机）
    // 接收方不在本地MessageServer在线列表上（按照第一阶段的异构通信算法，直接发往APP服务端）
    if (config.im_bridge_enabled && !userProcessor.isOnline(p.to)) {

        if (socket) {
            logger.debug('[IMSDK-G-MQ↑] >> [E]【C2C桥接】%s(name=%s) 正在发送给 %s，消息：%s 【他在线吗？FALSE】，马上转入桥接发送↗ ！'
                , socket.id, socket.userId, p.to, JSON.stringify(p));
        }
        // 可以认为当socket为null时即为服务端发送的消息（只有C2C模式的消息发送时，才能取到发送者的socket句柄）
        else {
            logger.debug('[IMSDK-G-MQ↑] >> [E]【S2C桥接】服务端 正在发送给 %s，消息：%s 【他在线吗？FALSE】，马上转入桥接发送↗ ！'
                , p.to, JSON.stringify(p));
        }

        // 桥接发送代码
        imMQProvider.publishBridge(p, function (result) {
            // FIXME 更优化的实现：桥接没有发送成功怎么办？实时回一条不成功的提示？这样体验不会好一点？（实时不成功再离线回调）
            // publish成功了
            if (result) {
                if (socket) {
                    logger.info('[IMSDK-G-MQ↑] >> [E]【C2C桥接】%s(name=%s) 发送给 %s 的消息：%s 已成功发出。【OK】'
                        , socket.id, socket.userId, p.to, JSON.stringify(p));
                }
                // 可以认为当socket为null时即为服务端发送的消息（只有C2C模式的消息发送时，才能取到发送者的socket句柄）
                else {
                    logger.info('[IMSDK-G-MQ↑] >> [E]【S2C桥接】服务端 发送给 %s 的消息：%s 已成功发出。【OK】'
                        , p.to, JSON.stringify(p));
                }
            }
            // publish失败
            else {
                if (socket) {
                    logger.warn('[IMSDK-G-MQ↑] >> [E]【C2C桥接】%s(name=%s) 发送给 %s 的消息：%s 没有桥接发送成功【将通过离线回调通知应用层】。'
                        , socket.id, socket.userId, p.to, JSON.stringify(p));
                }
                // 可以认为当socket为null时即为服务端发送的消息（只有C2C模式的消息发送时，才能取到发送者的socket句柄）
                else {
                    logger.warn('[IMSDK-G-MQ↑] >> [E]【S2C桥接】服务端 发送给 %s 的消息：%s 没有桥接发送成功【将通过离线回调通知应用层】。'
                        , p.to, JSON.stringify(p));
                }

                // @@ 通过MQ中间件桥接发送的C2C消息发送失败后的回调实现！（应用层可能需要做离线消息处理哦！）
                if (arg_callBack_processC2COfflineMessage) {
                    arg_callBack_processC2COfflineMessage(p);
                }
                // @@ 如果没有指定arg_callBack_processC2COfflineMessage，则用默认的callBack_processC2COfflineMessage作为回调处理
                else {
                    if (callBack_processC2COfflineMessage)
                        callBack_processC2COfflineMessage(p);
                }
            }
        });
    }
    // ** 【本机在线或其它情况下则直接在本机范围内发送和处理】
    // 接收方在本MessageServer的在线列表中
    else{//if(userProcessor.isOnline(p.to)){

        if(socket){
            logger.debug('[IMSDK-G] >> [E]【C2C】%s(name=%s) 正在发送给 %s，消息：%s 【他在线吗？TRUE】，马上进行在线直发-- ！'
                , socket.id, socket.userId, p.to, JSON.stringify(p));
        }
        // 可以认为当socket为null时即为服务端发送的消息（只有C2C模式的消息发送时，才能取到发送者的socket句柄）
        else{
            logger.debug('[IMSDK-G] >> [E]【S2C】服务端 正在发送给 %s，消息：%s 【他在线吗？TRUE】，马上进行在线直发-- ！'
                , p.to, JSON.stringify(p));
        }

        // 发送消息（此方法内的sendData实际上会通过回调处理在线和不在线的情况，只管调用就是了）
        //return sendDataLocal4OnlineOrOffline(socket, p
        //    , arg_callBack_processC2COfflineMessage?arg_callBack_processC2COfflineMessage
        //        :callBack_processC2COfflineMessage);
        return sendDataLocal4OnlineOrOffline(socket, p
            , arg_callBack_processC2COfflineMessage);
    }
}


/**
 * 外部方法：用于创建本服务端SDK的实例。
 *
 * @param srv
 * @param opts
 */
exports.createServer = function(srv, opts){
    // 创建socket.io服务端实例
    socketio = require('socket.io')(srv, opts);
    // 初始化sdk
    _init();
};

/**
 * 外部方法：一个发送（仅限于本机用户）消息的方便方法，以指定user_id的名义向另一个用户发送自定义typeu类型的数据，
 * 支持服务端中转发送(C2C)、服务器主动发送(S2C).
 *
 * @param from_user_id 必填参数，表示发出人的唯一id，如果是以服务器的名义发出的请填‘0’
 * @param to_user_id 必填参数，表示接收人的唯一id，此参数可以为任何有意义的除已之外的用户id但肯定不应该为‘0’
 * @param dataContent 必填参数，表示发送的数据内容
 * @param typeu 非必须参数，应用层专用字段——用于应用层存放聊天、推送等场景下的消息类型，由应用层自行决定。注意：此值为-1时表示未定义。
 *              MobileIMSDK_X框架中，本字段为保留字段，不参与框架的核心算法，专留用应用层自行定义和使用。
 * @param fnProcessOfflineMessage 非必须参数，用于指定消息没有在线发送成功时的离线消息回调处理，如果未设置本参数
 *                                则本方法将自动使用框架级的离线回调 callBack_processC2COfflineMessage，否则
 *                                优先使用你指定的回调函数（场景可能是这样的：或许您通过服务器发出的某些消息并不需
 *                                要离线处理，那么你可以设为null或自定义一个你要的回调函数即可）
 * @returns {boolean}
 * @see {send-util-local.sendData4(..)}
 */
// FIXME 存在一种情况：即消息的接收人在web端不在线，但可能在app端在线，但SDK目前因无全局的在线列表机制，所以跨机桥接不够优雅，暂时就先这样吧，二阶段时再优化！
exports.publicSendDataLocal = function(from_user_id
    , to_user_id, dataContent, typu, fnProcessOfflineMessage){
    return SendUtilsLocal.sendData4(userProcessor, from_user_id
        , to_user_id, dataContent, typu
        , fnProcessOfflineMessage?fnProcessOfflineMessage:callBack_processC2COfflineMessage);
};

/**
 * 外部方法：一个发送（本机+跨机器通信）消息的方便方法，此方法封装了服务端中转发送(C2C)、服务器主动发送(S2C)
 * 消息的所有逻辑（包括当接收者不在本机在线列表时的桥接处理等）。
 * <p>
 * <b>提示：</b>本方法可用于应用层调用，从而实现服务端向客户端主动发送消息的能力（且支持跨机器的消息能力）。
 *
 * @param socket 消息发起者的socket句柄（而非接收者哦），此句柄在方法中主要用于log信息的提取。本参数非必须项，可为null，
 * 当服务端主动发起消息（即S2C模式的消息，而非C2C）时本socket参数请填null就行了
 * @param p
 * @param arg_callBack_processC2COfflineMessage 非必须参数，用于指定消息没有在线发送成功时的离线消息回调处理
 */
exports.publicSendDataGlobal = sendDataGlobal;

/**
 * 外部方法：用于设置连接认证时的认证和处理回调函数。
 * 开发者可在回调函数中实现用户、密码验证或其它安全机制的处理，本回调用于与服务器的合法通道建立前。
 *
 * @param fn fn的参数：是“fn(loginInfo, function (err, authorized))”，本回调的实现者只需处理参数loginInfo!
 *           fn的参数loginInfo补充说明：客户传过来的用于认证的JSON对象，具体内容由客户端指定，本回调将原样
 *                    传过来(必须存在名为loginUserId(值必须不可为空且全局唯一)、loginToken(可选参数)的属性)
 *           fn返回值：返回true表示认证成功，否则表示认证失败！
 */
exports.setCallBack_checkAuthToken = function (fn){
    callBack_checkAuthToken = fn;
};

/**
 * 外部方法：用于用户认证成功后将调用本回调函数。
 * 补充说明：本回调将由应用层实现，通常是根据登陆信息到DB这些地方查询处理
 *          好个人认证好的信息，以便存储到用户的socket会话中备用。
 * 建议用途：开发者可在此回调中放入自已需要的更新多信息，因为此回调的结果将会被放于客户端的socket连接会话中，
 *          后绪的其它回调中将可拿到此值，此值的存放相当于Web应用中将用户的个人信息存放在Session中的属性。
 *
 * @param fn fn的参数：是“fn(loginInfo)”
 *           fn返回值：返回开发者更改后的个人认证信息（开发者要可能需要自已添加一些内容等），如不想处理则原样返回吧
 */
exports.setCallBack_authedInfo = function (fn){
    callBack_authedInfo = fn;
};

/**
 * 外部方法：用于设置用户上线通知回调函数。
 *
 * @param fn fn的参数：是“fn(socket.userId, socket)”
 *           fn返回值：无
 */
exports.setCallBack_userOnline = function (fn){
    callBack_userOnline = fn;
};

/**
 * 外部方法：用于设置用户离线通知回调函数。
 *
 * @param fn fn的参数：是“fn(socket.userId, socket)”
 *           fn返回值：无
 */
exports.setCallBack_userOffline = function (fn){
    callBack_userOffline = fn;
};

/**
 * 外部方法：用于设置收到C2S类型的消息通知回调函数。
 *
 * @param fn fn的参数：是“fn(Protocal, 发送者的socket句柄)”
 *           fn返回值：无
 */
exports.setCallBack_receivedC2SMessage = function (fn){
    callBack_receivedC2SMessage = fn;
};

/**
 * 外部方法：用于设置处理完C2C消息转发的通知回调函数。
 * 补充说明：回调函数 callBack_transferedC2CMessage 的调用时机是当客户端间的消
 *         息被服务端转发成功后回调的，此回调方法内可以由应用层对已成功转发完成的
 *         消息进行额外的处理：比如对用户间的聊天消息存储起来（比如像QQ样作消息漫
 *         游等）、对用户的聊天消息进行额外的审查等等，反正由应用层决定。
 *
 * @param fn fn的参数：是“fn(Protocal)”
 *           fn返回值：无
 */
exports.setCallBack_transferedC2CMessage = function (fn){
    callBack_transferedC2CMessage = fn;
};

/**
 * 外部方法：用于设置C2C类型消息离线处理回调函数。
 * 建议用途：开发者可在此方法中处理用户不在线情况下未实时送的消息，比如将它们离线存储于DB中。
 *
 * @param fn fn的参数：是“fn(Protocal)”
 *           fn返回值：无
 */
exports.setCallBack_processC2COfflineMessage = function (fn){
    callBack_processC2COfflineMessage = fn;
};





